2011-06-19 151 views
5

我有一個名爲Rails Across Europe的Facebook遊戲。它是用PHP/MySQL/Facebook Javascript編寫的。我的遊戲中缺乏的一個重要功能是交互式用戶教程。我有一個截屏視頻,但我認爲它不夠有用。我注意到,大多數開始遊戲的用戶在放棄之前只能玩一兩圈。這是一個複雜的遊戲,它會從互動教程中受益匪淺。創建Javascript遊戲教程

問題是,我不知道如何創建這樣的教程。遊戲包含歐洲地圖,包含歐洲城市,鐵路線(如軌道)和城市提供的商品。玩家應該建立連接城市的軌道,沿着軌道駕駛他的訓練場,在一個城市拿起貨物,然後將它們運送到另一個對貨物有需求的城市,然後他將得到報酬。

該遊戲包含許多不同的事件處理程序,如建築物軌道,移動火車,在城市裝載和卸載貨物等等。

我正在努力構建本教程,以便它與用戶的操作保持同步(反之亦然),以及如何確定用戶是否採取了允許教程繼續前進的正確操作下一步以及如何知道下一步是什麼。

這裏是我的前端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

讓玩家學習的一個好方法就是讓三個第一關是非常明顯的,並且讓玩家一次實驗一個或兩個新功能。例如,它可能是一級:建立兩個車站並通過鐵路連接。爲了更簡單,甚至可能會禁用功能尚未使用的功能。 – GameAlchemist

回答

6

這將需要一段時間,任何人都可以理解你的代碼,所以我認爲這是一個有點難以爲您的具體情況提供準確的代碼示例,但對於一些普遍的想法...

本教程中的每一步都可能有一組要求。點擊此按鈕,執行此操作。因此,要知道用戶何時完成了某些操作,您需要在這些操作上添加事件監聽器,並讓他們更改當前「步驟」的狀態。

一旦你的步驟的要求得到滿足,它將被簡單地替換爲下一步。此時,事件處理程序等將被更新以跟蹤新步驟的要求。

例如,假設您有一個步驟,用戶必須建立從A到B的軌道,然後通過它運行火車。在這樣的情況下,您可能會要求列車必須前往A,然後再前往B.因此,您的比賽應該在列車上有某種事件到達指定的車站,並且您將跟蹤此事件。

希望這會有所幫助。

+0

你說得對。我想添加一個新的'tutorialEventHandler',但是當我實現它時,我意識到我可以使用現有的事件處理程序和全局教程數據結構來跟蹤教程的哪一步。這似乎工作得很好。謝謝你的幫助。 –