2011-06-19 18 views
5

मेरे पास यूरोप भर में रेल का शीर्षक वाला एक फेसबुक गेम है। यह PHP/MySQL/फेसबुक जावास्क्रिप्ट में लिखा गया है। मेरे गेम में कमी की एक बड़ी विशेषताएं एक इंटरैक्टिव यूजर ट्यूटोरियल है। मेरे पास एक स्क्रीनकास्ट है, लेकिन मुझे नहीं लगता कि यह काफी मददगार है। मैंने देखा है कि अधिकतर उपयोगकर्ता जो गेम शुरू करते हैं, छोड़ने से पहले केवल एक या दो मोड़ खेलते हैं। यह एक जटिल गेम है और यह एक इंटरैक्टिव ट्यूटोरियल से बहुत फायदा होगा।जावास्क्रिप्ट गेम ट्यूटोरियल बनाना

समस्या यह है कि मुझे इस तरह कोई ट्यूटोरियल बनाने के बारे में कोई संकेत नहीं है। इस खेल में यूरोपीय शहरों, रेल लाइनों (जैसे ट्रैक), और शहरों द्वारा आपूर्ति किए गए सामान वाले यूरोप का नक्शा शामिल है। खिलाड़ी को शहरों से जोड़ने के लिए ट्रैक बनाना, ट्रैक के साथ अपने निशान को नेविगेट करना, एक शहर में सामान लेना, और उन्हें दूसरे शहर में पहुंचा देना चाहिए, जहां माल की मांग है, जहां उसे भुगतान किया जाएगा।

इस खेल में अन्य चीजों के साथ शहरों में कार्गो लोड करने, ट्रेन को स्थानांतरित करने और लोड करने जैसी चीजों के लिए कई अलग-अलग ईवेंट हैंडलर शामिल हैं।

मैं इस ट्यूटोरियल को कैसे व्यवस्थित करने के लिए संघर्ष कर रहा हूं, इसलिए यह उपयोगकर्ता के कार्यों (और इसके विपरीत) के साथ समन्वय में रहता है और यह निर्धारित करने के लिए कि उपयोगकर्ता ने सही कार्रवाई की है या नहीं, जो ट्यूटोरियल को आगे बढ़ने की अनुमति देगी अगला कदम और कैसे पता चलेगा कि अगला कदम क्या है।

यहाँ मेरी सामने के अंत js कोड के नमूने हैं:

var openCargoHolds = 0; 
var cargoHoldsUsed = 0; 
var loadCargoDialog = null; 
var isIE = false; 
function setBrowserIsIE(value) { 
    isIE = value; 
} 
function moveTrainAuto() { 
//debugger; 
//consoleTime('moveTrainAuto'); 
consoleLog('moveTrainAuto'); 
    var ajax = new Ajax(); 
    ajax.responseType = Ajax.JSON; 
//consoleTime('moveTrainAuto::move-trains-auto'); 
    ajax.ondone = function(data) { 
//consoleTimeEnd('moveTrainAuto::move-trains-auto'); 
//consoleTimeEnd('moveTrainAuto::get-track-data'); 
//debugger; 
    var trackColor = (data.route_owned) ? '#FF0' : '#888'; 
    var trains = []; 
    trains[0] = data.train; 
    removeTrain(trains); 
    drawTrack(data.y1, data.x1, data.y2, data.x2, trackColor, trains); 
//debugger; 
    if(data.code == 'UNLOAD_CARGO') { 
consoleLog('moveTrainAuto::unloadCargo'); 
     //unloadCargo(); 
     //myEventMoveTrainManual(null); //continue moving train until final destination is reached 
     moveTrainManual(); 
    } else if (data.code == 'MOVE_TRAIN_AUTO') { // || data.code == 'TURN_END') { 
     moveTrainAuto(); 
    } else if (data.code == 'TURN_END') { 
consoleLog('moveTrainAuto::turnEnd'); 
     turnEnd(); 
    } else { 
     /* handle error */ 
    } 
    } 
    ajax.post(baseURL + '/turn/move-train-auto-track-data'); 
//consoleTimeEnd('moveTrainAuto'); 
} 
function moveTrainAutoEvent(evt) { 
//debugger; 
    //moveTrainAuto(); 
    //myEventMoveTrainManual(null, false); 
    moveTrainManual(); 
} 
function moveTrainManual() { 
//consoleTime('moveTrainManual'); 
consoleLog('moveTrainManual'); 
//debugger; 
    state = MOVE_TRAIN_MANUAL; 
    var ajax = new Ajax(); 
    ajax.responseType = Ajax.JSON; 

    if(!trainInTransit) { 
    var actionPrompt = document.getElementById('action-prompt'); 
    actionPrompt.setInnerXHTML('<span><div id="action-text">'+ 
     'Move Train: Select destination'+ 
     '</div>'+ 
     '<div id="action-end">'+ 
     '<form method="POST">'+ 
     '<input type="button" value="Replace Demands" id="replace-demands-btn" style="width: 130px;" />'+ 
     '<input type="button" value="Upgrade Train" disabled="disabled" id="upgrade-train-btn" class="btn" />'+ 
     '<input type="button" value="Build Track" id="build-track-btn" class="btn" />'+ 
     '<input type="button" value="Manage Cargo" id="manage-cargo-btn" class="btn" />'+ 
     '</form>'+ 
     '</div></span>'); 
    var actionButton = document.getElementById('build-track-btn'); 
    actionButton.addEventListener('click', moveTrainEventHandler); 
    actionButton = document.getElementById('replace-demands-btn'); 
    actionButton.addEventListener('click', moveTrainEventHandler); 
    actionButton = document.getElementById('upgrade-train-btn'); 
    actionButton.addEventListener('click', moveTrainEventHandler); 
    var loadCargoButton = document.getElementById('manage-cargo-btn'); 
    loadCargoButton.addEventListener('click', moveTrainEventHandler); 
    } else { 
    var actionPrompt = document.getElementById('action-prompt'); 
    actionPrompt.setInnerXHTML('<span><div id="action-text">'+ 
     'Train in-transit to final destination...</div></span>'); 
    } 
    ajax.ondone = function(data) { 
consoleLog('ajax.moveTrainManual'); 
    if(data.code == 'TURN_END') { 
consoleLog('moveTrainManual::turnEnd'); 
     turnEnd(); 
    } else { 
//debugger; 
     //myEventMoveTrainManual(null); 
    } 
    } 
    ajax.post(baseURL + '/turn/move-train-manual'); 
//consoleTimeEnd('moveTrainManual'); 
} 
function unloadCargo() { 
//debugger; 
consoleLog('unloadCargo'); 
    var actionPrompt = document.getElementById('action-prompt'); 
    actionPrompt.setTextValue('Unloading cargo...'); 
    var ajax = new Ajax(); 
    ajax.responseType = Ajax.JSON; 
    ajax.ondone = function(data) { 
//debugger; 
    if(data.unloadableCargo.length == 0) { 
consoleLog('unloadableCargo == 0'); 
     moveTrainManual(); 
     //loadCargo(); 
    } else { 
consoleLog('unloadable cargo='+dump(data.unloadableCargo)); 
     var i = 0; 
     var j = 0; 
     var ucCount = data.unloadableCargo.length; 
     for(i = 0; i < ucCount; i++) { 
     var cargoDialog = new Dialog(); 
     cargoDialog.showChoice('Unload Cargo', 'Unload ' + data.unloadableCargo[i].goods_name + ' at ' + data.unloadableCargo[i].city_name + ' for ' + data.unloadableCargo[i].payoff + 'M euros?'); 
     cargoDialog.iVal = i; 
     cargoDialog.onconfirm = function() { 
//consoleLog('iVal='+this.iVal); 
//consoleLog('unloadable cargo onconfirm='+dump(data.unloadableCargo)); 
      var ajax = new Ajax(); 
      ajax.responseType = Ajax.JSON; 
      var param = {"city_id": data.unloadableCargo[this.iVal].city_id, "goods_id": data.unloadableCargo[this.iVal].goods_id, "payoff": data.unloadableCargo[this.iVal].payoff}; 
      ajax.ondone = function(demandData) { 
      refreshDemands(); 
      // update balance 
      setHtmlBalance(demandData.balance); 
      if(demandData.post_to_wall) { 
       Facebook.streamPublish('', demandData.attachment, demandData.action_links); 
      } 

      ajax.responseType = Ajax.JSON; 
      //debugger; 
      ajax.ondone = function(data) { 
       if(!data.already_won && data.funds >= data.winning_balance) { 
       var dialog = new Dialog().showMessage('Congratulations!', 'You have earned over '+data.winning_balance+'M euros. You have won! You may continue playing or start a new game.'); 
       dialog.onconfirm = function() { 
        moveTrainManual(); 
       } 
       } 
       moveTrainManual(); 
      } 
      ajax.post(baseURL + '/turn/get-player-stats'); 
      } 
      ajax.post(baseURL + "/turn/do-unload-cargo", param); 
     } 
     cargoDialog.oncancel = function() { moveTrainManual(); } 
     } 
    } 
    } 
    ajax.onerror = function() { 
    var dialog = new Dialog().showMessage('Request taking too long', 'The system is taking too long to process this request. Please try refreshing the page. If this does not work, please Contact Us with a description of your problem. We are sorry for the inconvenience.'); 
    } 
    ajax.post(baseURL + '/turn/unload-cargo'); 
} 
function loadCargo() { 
//consoleLog('Entering loadCargo()'); 
    var actionPrompt = document.getElementById('action-prompt'); 
    actionPrompt.setTextValue('Loading cargo...'); 
    var ajax = new Ajax(); 
    ajax.responseType = Ajax.JSON; 
    ajax.ondone = function(data) { 
//consoleLog('Entering ondone for load-cargo'); 
//debugger; 
    ajax.responseType = Ajax.FBML; 
    ajax.ondone = function(fbjsData) { 
//consoleLog('Entering ondone for load-cargo-dialog-fbjs'); 
//debugger; 
     if(data.loadableCargo.length == 0) { 
//consoleLog('Calling moveTrainManual()'); 
     moveTrainManual(); 
     } else { 
//consoleLog('Instantiating loadCargoDialog'); 
     if(loadCargoDialog == null) { 
      loadCargoDialog = new Dialog(); 
      //if browser is IE, move dialog up 50px to compensate for bug that causes it to shift down the screen 
      if(isIE) { 
      //loadCargoDialog.setStyle('position', 'relative'); 
      //loadCargoDialog.setStyle('top', '-50px'); 
      } 
      loadCargoDialog.showChoice('Load Cargo', fbjsData, 'Minimize', 'Pass'); 
     } else { 
      if(isIE) { 
      //loadCargoDialog.setStyle('position', 'relative'); 
      //loadCargoDialog.setStyle('top', '-50px'); 
      } 
      loadCargoDialog.showChoice('Load Cargo', fbjsData, 'Minimize', 'Pass'); 
     } 
     var dlgPrefixString = document.getElementById('dlg-prefix-string').getValue(); 
     //var dlgPrefixString = dlgPrefixElem.getValue(); 
//consoleLog('Setting dlgBtnNew'); 
     var dlgBtnNew = document.getElementById(dlgPrefixString+'-load-new-submit'); 
     dlgBtnNew.cityId = data.loadableCargo.city_id; 
     dlgBtnNew.trainId = data.loadableCargo.train_id; 
     dlgBtnNew.prefixString = dlgPrefixString; 
     dlgBtnNew.loadCargoDialog = loadCargoDialog; 
     dlgBtnNew.addEventListener('click', cargoEventHandler); //loadNewCargo); 
//consoleLog('Setting dlgBtnDiscard'); 
     var dlgBtnDiscard = document.getElementById(dlgPrefixString+'-discard-existing-submit'); 
     dlgBtnDiscard.cityId = data.loadableCargo.city_id; 
     dlgBtnDiscard.trainId = data.loadableCargo.train_id; 
     dlgBtnDiscard.prefixString = dlgPrefixString; 
     dlgBtnDiscard.loadCargoDialog = loadCargoDialog; 
     dlgBtnDiscard.addEventListener('click', discardExistingCargo); 
     loadCargoDialog.onconfirm = function() { 
//consoleLog('Entering loadCargoDialog.onconfirm'); 
      // Submit the form if it exists, then hide the dialog. 
      loadCargoDialog.hide(); 
      actionPrompt = document.getElementById('action-prompt'); 
      actionPrompt.setInnerXHTML('<span><div id="action-text">'+ 
      'The "Load cargo" dialog has been minimized'+ 
      '</div>'+ 
      '<div id="action-end">'+ 
      '<form action="" method="POST">'+ 
      '<input type="button" value="Maximize" id="next-phase" onclick="loadCargo();" />'+ 
      '</form>'+ 
      '</div></span>'); 
      actionButton = document.getElementById('next-phase'); 
      actionButton.setValue('Maximize'); 
      actionButton.addEventListener('click', loadCargoEventHandler); 
//consoleLog('Exiting loadCargoDialog.onconfirm'); 
     }; 
     loadCargoDialog.oncancel = function() { 
//consoleLog('Entering loadCargoDialog.oncancel'); 
      moveTrainManual(); 
//consoleLog('Exiting loadCargoDialog.oncancel'); 
     } 
     } 
//consoleLog('Exiting ondone for load-cargo-dialog-fbjs'); 
    } 
    ajax.onerror = function() { 
     var dialog = new Dialog().showMessage('Request taking too long', 'The system is taking too long to process this request. Please try refreshing the page. If this does not work, please Contact Us with a description of your problem. We are sorry for the inconvenience.'); 
    } 
    ajax.post(baseURL + '/turn/load-cargo-dialog-fbjs', data); 
//consoleLog('Exiting ondone for load-cargo'); 
    } 
    ajax.onerror = function() { 
    var dialog = new Dialog().showMessage('Request taking too long', 'The system is taking too long to process this request. Please try refreshing the page. If this does not work, please Contact Us with a description of your problem. We are sorry for the inconvenience.'); 
    } 
    ajax.post(baseURL + '/turn/load-cargo'); 
//consoleLog('Exiting loadCargo'); 
} 
function loadCargoEventHandler(evt) { 
    if(evt.type == 'click') { 
    loadCargo(); 
    } 
} 
function trackEventHandler(evt) { 
    var x1 = evt.target.x1; 
    var x2 = evt.target.x2; 
    var y1 = evt.target.y1; 
    var y2 = evt.target.y2; 
    var cost = evt.target.cost; 
    var prefixString = evt.target.prefixString; 

    evt.target.payDialog.hide(); 

    ajax = new Ajax(); 
    ajax.responseType = Ajax.JSON; 

    switch(evt.target.getId()) { 
    case prefixString + '-confirm-pay-submit': 
     ajax.ondone = function() { 
     var empty = []; 
     drawTrack(parseInt(y1), parseInt(x1), parseInt(y2), parseInt(x2), '#FF0', empty); 
//new Dialog().showMessage('test', 'balance='+balance); 
     balance = balance - parseInt(cost); 
     setHtmlBalance(balance); 
     saveCityStartElem.setSrc(publicURL + '/images/city_marker.gif'); 
     saveCityStartElem = null; 
     var actionPrompt = document.getElementById('action-prompt'); 
     var innerHtml = '<span><div id="action-text">Build Track: Select a city where track building should begin</div>'+ 
         '<div id="action-end">'+ 
         '<form action="">'+ 
         '<input type="button" value="End Track Building" id="next-phase" onClick="moveTrainAuto()" />'+ 
         '</form>'+ 
         '</div></span>'; 
     actionPrompt.setInnerXHTML(innerHtml); 
     var btn = document.getElementById('next-phase'); 
     btn.addEventListener('click', moveTrainAutoEvent); 
     state = TRACK_CITY_START; 
     } 
     ajax.onerror = function() { 
     new Dialog().showMessage('Track Building Error', 'An error occured while building this track. Please try again.'); 
     } 
     ajax.post(baseURL + '/turn/build-track-confirmed', {"europass_used": 0}); 
     break; 

    case prefixString + '-cancel-pay-submit': 
     saveCityStartElem.setSrc(publicURL + '/images/city_marker.gif'); 
     saveCityStartElem = null; 
     var actionPrompt = document.getElementById('action-prompt'); 
     var innerHtml = '<span><div id="action-text">Build Track: Select a city where track building should begin</div>'+ 
         '<div id="action-end">'+ 
         '<form action="">'+ 
         '<input type="button" value="End Track Building" id="next-phase" onClick="moveTrainAuto()" />'+ 
         '</form>'+ 
         '</div></span>'; 
     actionPrompt.setInnerXHTML(innerHtml); 
     var btn = document.getElementById('next-phase'); 
     btn.addEventListener('click', moveTrainAutoEvent); 
     state = TRACK_CITY_START; 
     ajax.post(baseURL + '/turn/build-track-resume'); 
     break; 

    case prefixString + '-europass-pay-submit': 
     ajax.ondone = function() { 
     var empty = []; 
     drawTrack(parseInt(y1), parseInt(x1), parseInt(y2), parseInt(x2), '#FF0', empty); 
//new Dialog().showMessage('test', 'balance='+balance); 
     saveCityStartElem.setSrc(publicURL + '/images/city_marker.gif'); 
     saveCityStartElem = null; 
     var actionPrompt = document.getElementById('action-prompt'); 
     var innerHtml = '<span><div id="action-text">Build Track: Select a city where track building should begin</div>'+ 
         '<div id="action-end">'+ 
         '<form action="">'+ 
         '<input type="button" value="End Track Building" id="next-phase" onClick="moveTrainAuto()" />'+ 
         '</form>'+ 
         '</div></span>'; 
     actionPrompt.setInnerXHTML(innerHtml); 
     var btn = document.getElementById('next-phase'); 
     btn.addEventListener('click', moveTrainAutoEvent); 
     state = TRACK_CITY_START; 
     } 
     ajax.onerror = function() { 
     new Dialog().showMessage('Track Building Error', 'An error occured while building this track. Please try again.'); 
     } 
     ajax.post(baseURL + '/turn/build-track-confirmed', {"europass_used": 1}); 
     break; 
    } 
} 
function cargoEventHandler(evt) { 
    //new Dialog().showMessage('loadNewCargo', 'city id='+cityId+', train id='+trainId); 
//debugger; 
    var cityId = evt.target.cityId; 
    var trainId = evt.target.trainId; 
    var prefixString = evt.target.prefixString; 

    evt.target.loadCargoDialog.hide(); 

    switch(evt.target.getId()) { 
    case prefixString + '-load-new-submit': 
//debugger; 
     ajax = new Ajax(); 
     ajax.responseType = Ajax.JSON; 
     param = { 'load-cargo-submit': "Load new goods", 'city-id': cityId, 'train-id': trainId }; 
     ajax.ondone = function(data) { 
     openCargoHolds = data.openCargoHolds; 
     cargoHoldsUsed = 0; 
     ajax.responseType = Ajax.FBML; 
     param = { 'openCargoHolds': data.openCargoHolds, 'cityGoods': data.cityGoods, 'trainId': data.trainId }; 
     ajax.ondone = function(fbjsData) { 
    //debugger; 
      var dialog = new Dialog().showChoice('Load Cargo', fbjsData, 'Load cargo', 'Cancel'); 
      var numGoods = data.cityGoods.length; 
      for(var i = 1; i <= numGoods; i++) { 
      var decrementGoodsArrow = document.getElementById('goods-decrement-' + i); 
      decrementGoodsArrow.addEventListener('click', goodsAdjustmentHandler); 
      var incrementGoodsArrow = document.getElementById('goods-increment-' + i); 
      incrementGoodsArrow.addEventListener('click', goodsAdjustmentHandler); 
      } 
      dialog.onconfirm = function() { 
//debugger; 
      var goods = []; 
      var goodsIds = []; 
      numGoods = document.getElementById('goods-count').getValue(); 
      for(var i = 0; i < numGoods; i++) { 
       j = i + 1; 
       goods[i] = document.getElementById('goods-' + j).getValue(); 
       goodsIds[i] = document.getElementById('goods-id-' + j).getValue(); 
      } 
      var trainId = document.getElementById('train-id').getValue(); 
      param = { "goods": goods, "goods-id": goodsIds, "train-id": trainId }; 
      ajax.responseType = Ajax.JSON; 
      ajax.ondone = function(data) { 
       loadCargo(); 
      } 
      ajax.onerror = function() { 
       var dialog = new Dialog().showMessage('Request taking too long', 'The system is taking too long to process this request. Please try refreshing the page. If this does not work, please Contact Us with a description of your problem. We are sorry for the inconvenience.'); 
      } 
      ajax.post(baseURL + '/turn/do-load-cargo-new', param); 
      //dialog.hide(); 
      }; 
      dialog.oncancel = function() { 
      loadCargo(); 
      } 
     } 
     ajax.post(baseURL + '/turn/load-cargo-new-dialog-fbjs', param); 
     } 
     ajax.post(baseURL + '/turn/load-cargo-select', param); 
     break; 
    case prefixString + '-discard-existing-submit': 
     ajax = new Ajax(); 
     ajax.responseType = Ajax.JSON; 
     param = { 'load-cargo-submit': "Discard existing goods", 'city-id': cityId, 'train-id': trainId }; 
     ajax.ondone = function(data) { 
     ajax.responseType = Ajax.FBML; 
     param = { 'openCargoHolds': data.openCargoHolds, 'trainGoods': data.trainGoods, 'trainId': data.trainId }; 
     ajax.ondone = function(fbjsData) { 
      var dialog = new Dialog().showChoice('Discard Cargo', fbjsData, 'Discard cargo', 'Cancel'); 
      dialog.onconfirm = function() { 
//debugger; 
      var goods = []; 
      var goodsIds = []; 
      numGoods = document.getElementById('goods-count').getValue(); 
      for(var i = 0; i < numGoods; i++) { 
       j = i + 1; 
       goods[i] = document.getElementById('goods-' + j).getValue(); 
       goodsIds[i] = document.getElementById('goods-id-' + j).getValue(); 
      } 
      var trainId = document.getElementById('train-id').getValue(); 
      param = { "goods": goods, "goods-id": goodsIds, "train-id": trainId }; 
      ajax.responseType = Ajax.JSON; 
      ajax.ondone = function(data) { 
       loadCargo(); 
      } 
      ajax.post(baseURL + '/turn/do-load-cargo-discard', param); 
      //dialog.hide(); 
      }; 
      dialog.oncancel = function() { 
      loadCargo(); 
      } 
     } 
     ajax.post(baseURL + '/turn/load-cargo-discard-dialog-fbjs', param); 
     } 
     ajax.post(baseURL + '/turn/load-cargo-select', param); 
     break; 
    } 
    return true; 
} 
+0

खिलाड़ी सीखने का एक अच्छा तरीका यह कहना है कि तीन पहले स्तर काफी स्पष्ट हैं और खिलाड़ी एक समय में एक या दो नई सुविधा का प्रयोग करते हैं। यह उदाहरण स्तर 1 के लिए हो सकता है: दो स्टेशनों का निर्माण करें और उन्हें रेल द्वारा जोड़ा गया है। सुविधाओं को अभी तक उपयोगी नहीं है और अधिक सादगी के लिए भी अक्षम किया जा सकता है। – GameAlchemist

उत्तर

6

यह कुछ समय ले किसी को भी अपने कोड को समझने के लिए होगा, इसलिए मुझे लगता है कि अपने विशिष्ट मामले के लिए सटीक कोड उदाहरण प्रदान करने के लिए थोड़ा मुश्किल है , लेकिन कुछ सामान्य विचारों के लिए ...

ट्यूटोरियल में प्रत्येक चरण उदाहरण के लिए आवश्यकताओं का एक सेट हो सकता है। इस बटन पर क्लिक करें, यह क्रिया करें। तो यह जानने के लिए कि उपयोगकर्ता ने कुछ कब किया है, आपको उन कार्रवाइयों पर ईवेंट श्रोताओं को जोड़ना होगा और उन्हें वर्तमान "चरण" की स्थिति बदलनी होगी।

एक बार आपके कदम की आवश्यकताओं को पूरा करने के बाद, इसे तुरंत अगले चरण में बदल दिया जाएगा। इस बिंदु पर, ईवेंट हैंडलर और ऐसे को नए चरण की आवश्यकताओं पर नज़र रखने के लिए अपडेट किया जाएगा।

उदाहरण के लिए, मान लीजिए कि आपके पास एक ऐसा कदम होगा जहां उपयोगकर्ता को ए से बी तक ट्रैक बनाना होगा, और उसके बाद एक ट्रेन चलाएं। इस तरह के मामले में, आपको आवश्यकता हो सकती है कि एक ट्रेन को ए, और बाद में बी तक जाना चाहिए। इसलिए आपके गेम में किसी निर्दिष्ट स्टेशन तक पहुंचने वाली ट्रेन पर किसी प्रकार की घटना होनी चाहिए, और आप इस घटना को ट्रैक करेंगे।

उम्मीद है कि इससे मदद मिलती है।

+0

आप सही हैं। मैंने एक नया 'ट्यूटोरियलइवेंटहैंडलर' जोड़ने के बारे में सोचा लेकिन जैसा कि मैं इसे कार्यान्वित कर रहा था, मुझे एहसास हुआ कि मैं अपने मौजूदा ईवेंट हैंडलर और वैश्विक ट्यूटोरियल डेटा स्ट्रक्चर का उपयोग कर सकता हूं ताकि यह पता चल सके कि ट्यूटोरियल किस चरण पर है। ऐसा लगता है कि यह काफी अच्छी तरह से काम कर रहा है। आपकी सहायता के लिए धन्यवाद. –

संबंधित मुद्दे