<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>

電子發燒友App

硬聲App

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示
電子發燒友網>電子資料下載>電子資料>適用于Amazon Alexa的游戲Speed Tap開源

適用于Amazon Alexa的游戲Speed Tap開源

2022-12-28 | zip | 0.04 MB | 次下載 | 免費

資料介紹

描述

概括

Speed Tap 是一款適用于 Amazon Alexa 的游戲,它使用 Echo Buttons 來測試玩家的反應時間和注意力。該按鈕會在隨機顏色之間循環,玩家必須在按鈕為綠色時點擊該按鈕。隨著游戲的進行,燈光速度加快,難度也越來越大。

該游戲結合了許多Alexa APIAWS 服務來創造引人入勝的體驗,同時保持簡單的游戲玩法。這個故事將重點解釋為什么需要這些以及它們的大致工作原理,以便其他開發人員了解什么是可能的以及如何進行。

這項技能是使用 Node、AWS 服務和alexa-app框架從頭開始構建的。

關鍵概念和獨特功能

從較高的層面來看,這些是使這款游戲無論是作為 Alexa Skill 還是從開發/編程的角度來看都獨一無二的東西:

  • 玩家與所有 Alexa 用戶競爭以獲得世界紀錄,這使得它比僅僅與自己對戰更有趣和互動。
  • AlexaSpeedTap.com的集成網站具有一個自動更新的排行榜,顯示每個玩家的高分。或者,玩家可以通過使用 Alexa API 的游戲本身在排行榜上無縫連接他們的真實姓名。
  • In-Skill-Purchases允許玩家購買額外的生命以在他們錯過后繼續游戲。
  • Alexa 聲音庫隨機短語Speechcons(感嘆詞)用于使體驗更加引人入勝和生動,并賦予其一些個性。孩子們特別喜歡它。

架構和 AWS 服務

?
poYBAGOrnF-ANzi3AACwMoGZYEo890.png
AWS 架構
?

拉姆達

游戲的所有代碼都在 AWS Lambda 中。它是使用 alexa-app 框架在 Node.js 中編寫的。Alexa 事件(玩游戲的用戶)和 CloudWatch Events 使用相同的 lambda 函數代碼,這會觸發后臺任務來更新排行榜。

動態數據庫

個人球員和全球世界紀錄統計數據都存儲在 DynamoDB 中。對 DDB 的訪問是使用 Amazon 的 aws-sdk 包完成的,我在其頂部編寫了一個自定義層以簡化 API 并在現有的 Promise 接口上提供一個異步/等待接口。

每次交互時,用戶數據和狀態都會保存到 DDB,從而使游戲能夠以隨著時間的推移和適當的方式與用戶進行交互(請參閱下面的用戶體驗部分)。

S3

Lambda 函數每 5 分鐘生成一個包含所有高分和當前世界紀錄數據的 JSON 文件,并將其存儲在公共 S3 存儲桶中。然后通過 AJAX 從網站加載此 JSON 文件以顯示排行榜并以大文本宣布當前的記錄保持者。

后臺任務 - CloudWatch Events

需要一個“后臺任務”來定期更新包含網站數據的 JSON 文件。由于無法按計劃觸發 Lambda 函數,因此使用 CloudWatch Events 觸發觸發 Lambda 函數的事件。

然后,Lambda 函數代碼會檢測它是由 Alexa 還是 CloudWatch Events 觸發的,并做出相應的行為。

ALEXA API 和功能

許多 Alexa API 和功能被用來創建這個游戲。以下是使用的內容和原因的摘要。

小工具API

Gadgets API需要與 Echo Buttons 交互,實際上有兩種風格,它們都必須聲明為技能所需的接口。

GAME_ENGINE是用于監聽 Echo Buttons 事件的接口。

GADGET_CONTROLLER是用于實際設置按鈕本身的燈光模式的接口。

這是兩個不同的 API,要讓它們無縫地協同工作,一開始理解起來有點棘手。仔細閱讀文檔、了解每個文檔的局限性并在設計您的體驗時了解什么是可能的、什么是不可能的,這一點很重要。

客戶資料 API

Customer Profile API允許技能請求有關用戶的信息,例如姓名、電子郵件地址和電話號碼。使用時,該技能會在用戶的 Alexa 應用程序中向用戶出示一張卡片以授予權限,然后才能訪問此信息。如果用戶請求,Speed Tap 使用此 API 獲取用戶名以顯示在網站的排行榜上。

技能購買

In-Skill Purchases (ISP) API允許技能請求用戶使用真錢進行實際購買,以解鎖技能中的內容或功能。Speed Tap 使用“消耗品”ISP (CISP),這是可以購買和用完的物品,而不是一次性購買和訂閱,它們不會在使用時消失。

Speed Tap 使用戶能夠在點擊錯誤的顏色后繼續。每個新用戶都會獲得 5 條額外的生命,當他們離開后,他們可以選擇使用 CISP 再購買 10 條生命。

展示

Speed Tap 利用顯示接口 API與 Echo Show 和 Echo Spot 設備上的顯示功能進行交互,方法是在游戲開始時顯示彩色“啟動畫面”,并在每次按下按鈕時顯示當前回合。

?

隨著帶有顯示器的 Echo 設備變得越來越普遍,技能通過視覺提示和附加信息來補充其音頻輸出非常重要。

聲音庫

Alexa Skills Kit 聲音庫是可用于任何技能的音頻片段集合。這些音頻文件由亞馬遜存儲,可以插入到任何響應中,而無需技能作者創建、存儲或交付它們。例如:


		src='soundbank://soundlibrary/ui/gameshow/amzn_ui_sfx_gameshow_countdown_loop_32s_full_01'/> 
		

隨著用于多種用途的聲音樣本的選擇越來越多,這個庫為技能作者提供了一種簡單的方法來為他們的技能添加一點額外的個性和樂趣。Speed Tap 在等待用戶按下按鈕以增加戲劇性和張力時將這些聲音用于音樂,以及用于積極和消極事件。

演講者

Alexa 語音輸出可以比默認的文本轉語音更具表現力的方式說出某些詞。這些“ Speechcons ”僅限于每種支持語言的一組特定單詞,并使用標記插入到您的 SSML 響應中:

<say-as interpret-as="interjection">abracadabra!say-as> 

Speed Tap 使用 Speechcons 響應每個按鈕按下,讓游戲對用戶來說更有趣,并增加一點多樣性。玩游戲的孩子特別喜歡有趣的輸出。

Alexa 技能活動

技能事件API允許技能響應更改或事件,而無需用戶直接與技能交互。例如,當用戶啟用或禁用技能時,可以通知該技能。

當用戶使用手機上的 Alexa 應用程序啟用或禁用對其客戶資料信息的訪問時,Speed Tap 使用 Skill Events API 立即做出響應。收到此事件后,Speed Tap 會立即調用 API 獲取用戶姓名,因此無需用戶再次玩游戲即可更新排行榜。

用戶體驗

語音應用程序的用戶體驗至關重要且微妙。網絡和移動等顯示媒體不同,音頻需要更長的時間來呈現信息,并且用戶無法處理選項列表或掃描屏幕以了解他們下一步想做什么。

Speed Tap 實施了許多改進用戶體驗的方法,這些方法雖然不那么明顯,但卻產生了很大的不同。

共享的全球經驗

與移動應用程序不同,大多數 Alexa 游戲都是單人游戲,不與其他玩家社區互動。用戶是孤立的,通常玩游戲是為了個人高分或自己探索游戲。

但是……和別人一起玩游戲更有趣!

Speed Tap 朝著這個方向邁出了獨特的一步,讓玩家可以看到他們的分數與其他人的比較情況。網站上的排行榜列出了每個玩家的個人最高分以及他們用了多少次連續得分來達到這一目標。添加你的真實姓名的能力讓你有吹牛的權利,看看你是否在打敗你的朋友。

玩家知道什么與實際上是什么?

為用戶提供相關信息的一個重要部分是跟蹤他們所知道的,而不是實際情況。

在 Speed Tap 中,這與世界紀錄有關。當用戶玩游戲時,他們可能會被告知當前的世界紀錄是 25 次點擊。但下次他們比賽時,世界紀錄可能會增加到 30,他們很想知道。

懶人技能只會在用戶每次開始游戲時告訴用戶世界紀錄。但為了獲得更好的體驗,該技能需要知道用戶上次被告知的世界紀錄是什么,并且只有在更改時才告訴他們。通過這種方式,他們可以獲得他們想要的信息,但如果相同,則不必每次都被迫忍受關于當前世界紀錄的相同句子。

Speed Tap 通過保留兩個獨立的數據區域來跟蹤此類事件。一個保存當前的真實信息(例如當前的世界紀錄和持有者),而另一個保存相同的信息,但處于用戶最后知道的狀態。然后將這些進行比較以確定應該告訴用戶什么。代碼如下所示:

sayif `Your high score is ${user.high_score} `;
// Check to see if there is a new world record to inform the user about
if (game.world_record) {
 if (user.world_record && user.world_record < game.world_record) {
   say `and there is a new world record. The highest score is now ${game.world_record}`;
 }
 else if (game.world_record_user && game.world_record_user===request.data.session.userId) {
   say `and you still hold the world record`;
 }
 else {
   say `and the world record is ${game.world_record}`;
 }
 user.world_record = game.world_record;
}

增量體驗/會話跟蹤

由于用戶會多次玩游戲,因此不應每次都向他們展示冗長的音頻介紹。當他們多次使用特性時,不應該要求他們忍受對他們已經理解的東西的解釋。

出于這個原因,跟蹤用戶與技能交互時的體驗對于技能來說很重要,這樣技能就知道何時可以縮短交互。這正是人類所做的,通過這樣做,一項技能似乎更自然。

Speed Tap 為每個用戶保留一個“體驗”對象,該對象隨依賴于它的每種交互類型進行更新。然后,根據用戶當前的體驗水平更改輸出。下面是一些示例代碼:

// Open with a more different intro depending on how often the user has played
if (request.experience("session_count",false) <= 1) {
 say `Welcome to ${speedtap}. This is a game of quick reactions and concentration. Would you like to hear a quick explanation of how to play?`;
}
else {
 if (request.experience("session_count",false) < 4) {
   say `Welcome back to ${speedtap}.`;
 }
 else {
   response.say(`Welcome back.`);
 }
} 

experience 對象還用于確定是否應該給出有關排行榜如何工作、所需權限等的解釋。

隨機輸出

一遍又一遍重復相同輸出的技能變得非常重復并且感覺不自然。至少隨機化部分輸出很重要,這樣技能就不會感覺那么“僵硬”。

對于 Speed Tap,簡單的隨機化是使用我在其他技能中實施的文本后處理技術完成的。

輸出文本時,{braces} 可以放在作為隨機同義詞候選詞的任何單詞或短語周圍。然后,創建一個可能的同義詞列表,并在輸出字符串中僅使用主鍵。單詞會自動隨機化,使技能感覺更自然,不那么機械化。

// The synonym list
const outputSynonyms = {
 "Okay, ": ["Okay, ","Alright, ",""]
};
// A method in the response object
"randomizeSynonyms": function(synonyms) {
 try {
   let ssml = this.response.response.outputSpeech.ssml;
   ssml = ssml.replace(/\{([^\}]+)\}/g, function (m, m1) {
     if (synonyms && synonyms[m1]) {
       let s = synonyms[m1];
       if (s.length) {
         // simple array of synonyms
         return s[Math.floor(Math.random() * s.length)];
       }
     }
     return m1;
   });
   this.response.response.outputSpeech.ssml = ssml;
 } catch(e) { }
}
// Example
response.say("{Okay, }let's play."); // Could be: "Alright, let's play" or "let's play" 
// Randomize synonyms in the output during app.post()
response.randomizeSynonyms(outputSynonyms);

執行

本節將深入探討該技能的實現細節,并展示演示每種功能如何實現的代碼片段。

觸發切換

Lambda 函數需要根據它是從 CloudWatch Events(更新排行榜)還是從 Alexa 觸發而表現不同。Lambda 函數處理程序使用以下代碼進行切換:

// connect to lambda
exports.handler = function(event, context, callback) {
 if (event && "aws.events"===event.source) {
   // Scheduled Event!
   app.scheduled(event).then((response)=>{
     callback(null,response);
   }).catch((e)=>{
     callback(e);
   });
 }
 else {
   // Alexa Request
   log("Alexa Request");
   app.handler(event, context, callback);
 }
}; 

按鈕偵聽器事件

技能使用 Gadgets API 監聽按鈕事件。該技能返回一個指令,定義觸發技能的確切條件。這是一個示例按鈕偵聽器指令,它將導致在按下按鈕或超時時調用技能。

{
 "type" : "GameEngine.StartInputHandler",
 "timeout" : 25000,
 "proxies" : ["button"],
 "recognizers" : {
   "button_down_recognizer" : {
     "type" : "match",
     "anchor" : "end",
     "fuzzy" : false,
     "pattern" : [{
         "action" : "down"
       }
     ]
   }
 },
 "events" : {
   "button_down_listener" : {
     "meets" : ["button_down_recognizer"],
     "reports" : "matches",
     "shouldEndInputHandler" : true
   },
   "timeout" : {
     "meets" : ["timed out"],
     "reports" : "history",
     "shouldEndInputHandler" : true
   }
 }
} 

光指令

有幾種不同格式的 GadgetController.SetLight 指令可以根據按鈕的狀態更改按鈕的顏色。API 文檔詳細介紹了這些狀態是什么以及它們如何工作。

這是一個循環顯示顏色并等待用戶在按鈕變為綠色時按下按鈕的示例指令。當代碼接收到按鈕按下事件時,它還會接收按鈕被按下時的顏色。這允許代碼檢查用戶是否在它為綠色時按下它。

{
 "type" : "GadgetController.SetLight",
 "version" : 1,
 "targetGadgets" : [],
 "parameters" : {
   "triggerEvent" : "none",
   "triggerEventTimeMs" : 0,
   "animations" : [
     {
       "repeat" : 255,
       "targetLights" : ["1"],
       "sequence" : [{
           "durationMs" : 1000,
           "blend" : false,
           "color" : "0000FF"
         }, {
           "durationMs" : 1000,
           "blend" : false,
           "color" : "FFA500"
         }, {
           "durationMs" : 1000,
           "blend" : false,
           "color" : "FF0000"
         }, {
           "durationMs" : 1000,
           "blend" : false,
           "color" : "FF00FF"
         }, {
           "durationMs" : 1000,
           "blend" : false,
           "color" : "00FF00"
         }
       ]
     }
   ]
 }
}

堅持

所有持久化都是使用 DynamoDB (DDB) 完成的。

每次調用技能時,它都會檢查會話是否有用戶對象。如果沒有,它會嘗試從請求中給定的 userId 的 DDB 加載它。如果用戶記錄存在,它會加載它并將其存儲在會話中,這樣就不需要在每次調用技能時都檢索它。

用戶數據在發生變化時會持久保存回 DDB。例如,新的高分、會話計數增量、使用額外的生命等。

在觸發 In-Skill Purchase 流程的情況下,實際需要技能退出,會話結束。然后,當玩家完成購買時,將創建一個新會話。出于這個原因,在某些情況下需要保留當前游戲的狀態。但在大多數情況下,當前游戲的回合和分數需要從用戶記錄中清除,而不是持久化。

持久化用戶記錄的通用函數會清除不應持久化的數據,并調用 DDB 持久化層來存儲數據。

async function persist_user(persist_game_state) {
 // Persist user session back to db if it has changed
 if (user) {
   let u = JSON.parse(JSON.stringify(user));
   if (!persist_game_state) {
     delete u.round;
     delete u.lives_used;
     delete u.state;
     delete u.buttonConnected;
   }
   delete u.game;
   delete u.listenerRequestId;
   await ddb.put(app.user_persistence_table, u);
 }
}

數據庫層

javascript 的 ask-sdk 模塊有很多很棒的功能,但它的 API 對我來說仍然太低級了。我圍繞 ask-sdk 函數編寫了一個包裝器,進一步抽象了功能。

包裝器所做的其中一件事是提取從 ask-sdk 函數返回的 Promises,而是公開異步函數,以便我的代碼可以使用 await。

包裝器代碼包含在源代碼中,我打算將其清理并作為 NPM 模塊分發。下面是一個示例函數,它簡化了從 DDB 中檢索單個記錄的過程:

'get': async function(table, keyAttribute, keyValue) {
 let params = {TableName:table, Key:{ [keyAttribute]:keyValue } };
 return docClient.get(params).promise()
   .then( (item)=> {
     if (!item || !item.Item) { return null; }
     return item.Item;
   });
}

會話/體驗維護

游戲源中的用戶對象包括一個“體驗”對象,用于存儲用戶在玩游戲時的體驗。作為此體驗對象的一部分,每次啟動游戲時都會增加 session_count 屬性。

體驗對象還包含跟蹤用戶是否已經聽到特定響應的鍵。如果他們有,那么下次觸發時他們會得到一個較短的版本。例如:

if (round===1 && request.experience('intro_1')) {
 say `That was easy, but now the lights will get a little faster every round. How far can you go? Keep going.`;
}
else if (round===1) {
 say `Nice, Keep going.`;
}

文本響應后處理

構建被翻譯成語音的文本輸出有一些煩惱,例如復數化和 is/are 等。Speed Tap 包含文本后處理功能,可大大簡化常見用例。每次調用技能后,都會自動對輸出 SSML 執行此后處理。

以下是后處理功能可以執行的操作的一些示例。

let coins=1;
say `There {are} ${coins} coin{s} left.`;

處理器處理 {are} 和 {s} 并查找附近的數字以確定應如何處理它們。在本例中,它看到“1”并且輸出為:

"There is 1 coin left."

如果 coins==2,那么同一個 say 調用的輸出將是:

"There are 2 coins left."

狀態和上下文意圖

意圖缺乏狀態和上下文。意思是,有全局 AMAZON.YesIntent 和 AMAZON.NoIntent 意圖,當用戶說“是”或“否”時會被觸發。但是這些意圖不知道問的是哪個問題,所以一種常見的方法是在每個處理程序中構建邏輯知道被問到的問題并采取適當的行動。

相反,我在 alexa-app 框架上構建了一個層,允許我創建“上下文意圖”。當用戶處于某種狀態時,是或否將在用戶所在的上下文中觸發函數,而不是全局意圖。

例如,如果詢問用戶是否要繼續,他們的會話會更新以反映他們處于“繼續”狀態,并且適當地處理是或否:

app.intentMap({
 "continue": {
   [YES]: async()=>{
     await continue_game();
   }
   ,[NO]: async()=>{
     await end_game();
   }
 }
});

技能購買

In-Skill Purchases 允許玩家購買額外的生命以在他們搞砸時繼續玩。這實際上是一個復雜的主題,有很多實施怪癖和細節。一些重要的注意事項值得一提:

  • ISP 只能使用 CLI 工具進行配置和部署。
  • 它將您的技能限制在美國地區(這就是 Speed Tap 不在英國的原因)。
  • 需要從技能代碼中進行 API 調用,以向其 API 端點發出 https 請求。
  • 當ISP流程發起后,技能完全退出,會話結束。Alexa 服務控制并引導用戶完成購買流程。當它完成時 - 無論成功還是失敗 - 技能將再次啟動,并帶有指示購買狀態的“Connections.Response”事件類型。
  • 該技能必須處理購買流程中的錯誤情況以及用戶是否拒絕購買。它還必須處理用戶在購買后要求退款的情況。
  • 對于消耗品 ISP,正如 Speed Tap 使用的那樣,該技能必須記住并存儲用戶購買了哪些物品以及他們還剩多少。Alex 服務不維護庫存。

要觸發 ISP 購買流程,必須從技能返回指令并且必須關閉會話。該指令如下所示:

{
 'type': 'Connections.SendRequest',
 'name': 'Buy',
 'payload': {
   'InSkillProduct': {
     'productId': "XYZ"
   }
 },
 'token': "arbitrary-token"
}

Alexa 技能活動

Alexa 技能事件允許技能在沒有用戶主動與技能交互時響應事件。對于 Speed Tap,它處理用戶授予或撤銷使用其真實姓名的權限的情況。

下面的代碼是 Speed Tap 如何響應此事件,使用 API 檢索用戶的真實姓名,更新他們的用戶記錄,并持久化它。

app.on('AlexaSkillEvent.SkillPermissionAccepted', async()=>{
 try {
   let user_id = request.data.context.System.user.userId;
   user = await ddb.get(app.user_persistence_table, "userid", user_id);
   let name = await app.api("/v2/accounts/~current/settings/Profile.name");
   user.name = name;
   user.linked = true;
   await persist_user();
 } catch(e) {
   console.log(e.message);
 }
});

為了響應這些事件,技能必須注冊它希望收到這些事件的通知。使用 CLI 時,這些存儲在 manifest.events.subscriptions 下的 skill.json 中:

      "subscriptions": [
       {
         "eventName": "SKILL_PERMISSION_ACCEPTED"
       },
       {
         "eventName": "SKILL_PERMISSION_CHANGED"
       }
     ]

聲音庫

聲音庫是一種非常好的方法,可以輕松地將聲音包含在技能中。這些聲音都按類別列在“聲音庫”頁面上。

您無需執行任何特殊操作即可使用這些聲音。只需找到您要使用的聲音,復制列出的 SSML 內容,然后將其插入到您的響應中。


		src='soundbank://soundlibrary/animals/amzn_sfx_bear_groan_roar_01'/> 
		

我假設使用該soundbank:協議可以最大限度地減少延遲,因為聲音文件存儲在靠近 Alexa 內部服務器的某些邊緣服務器上。

啟動畫面/顯示

當技能啟動時,會顯示一個圖形啟動畫面。這是通過使用 Display 接口在響應中返回指令來實現的。可重用函數將功能包裝在一個地方:

function display_splash_screen(request,response) {
 if (has_display(request)) {
   response.directive({
     "type" : "Display.RenderTemplate",
     "template" : {
       "type" : "BodyTemplate1",
       "backButton" : "HIDDEN",
       "backgroundImage" : {
         "contentDescription" : "",
         "sources" : [{
           "url" : "https://alexaspeedtap.com/splash.jpg",
           "size" : "MEDIUM"
         },{
           "url" : "https://alexaspeedtap.com/splash-square.jpg",
           "widthPixels":640,
           "heightPixels":640
         }
         ]
       }
     }
   });
 }
}

這不一定代表展示的最佳做法,但它可以完成工作。

使用 Display 指令時,技能必須檢測用戶的設備是否有屏幕,如果沒有則不發送指令,否則會拋出錯誤。has_display() 函數封裝了該檢查。

const has_display = function(request) {  try {    return !!request.data.context.System.device.supportedInterfaces.Display;  }  catch(e) { return false; }}; 

還必須為該技能注冊 RENDER_TEMPLATE 接口。如果跳過此步驟,任何返回 Display 指令的嘗試都將導致異常。

寫入 S3

當后臺任務運行并創建排行榜 JSON 文件時,必須從技能代碼中將其寫入 S3 存儲桶。

我寫了一個簡單的可重用函數來將任意 javascript 對象寫入我的存儲桶:

const putObjectToS3 = async function (o,filename) {
 let s3 = new AWS.S3({'region': 'us-east-1'});
 let params = {
   Bucket: "alexa-speed-tap",
   Key: filename,
   Body: JSON.stringify(o),
   ContentType: "application/json",
   CacheControl: "no-cache",
   ACL: "public-read"
 };
 return s3.putObject(params).promise();
}; 

CORS

由于排行榜 JSON 文件存儲在 AWS S3 上,在生成的 Amazon URL 上提供服務,瀏覽器將阻止它從其他域加載,例如 AlexaSpeedTap.com。

為了允許瀏覽器訪問不同服務器上的內容,托管服務器 (S3) 必須明確允許此類請求。

這可以使用如下策略在權限 --> CORS 配置下的 S3 存儲桶上進行配置。


<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
   <AllowedOrigin>*AllowedOrigin>
   <AllowedMethod>GETAllowedMethod>
   <MaxAgeSeconds>3000MaxAgeSeconds>
   <AllowedHeader>*AllowedHeader>
CORSRule>
CORSConfiguration>

結論

構建 Echo Buttons 是一個獨特的挑戰,因為 API 和實際的小工具功能有點棘手。但是,一旦將預期調整為實際可能的情況,并構建了基本功能,完善體驗和讓游戲變得有趣就相對簡單了。

盡管 Speed Tap 是一款易于玩和理解的游戲,但它使用了許多使其真正獨一無二的 Alexa 功能和概念,并為玩家提供了不同于其他游戲的體驗。

我希望你喜歡它!

?


下載該資料的人也在下載 下載該資料的人還在閱讀
更多 >

評論

查看更多

下載排行

本周

  1. 1山景DSP芯片AP8248A2數據手冊
  2. 1.06 MB  |  532次下載  |  免費
  3. 2RK3399完整板原理圖(支持平板,盒子VR)
  4. 3.28 MB  |  339次下載  |  免費
  5. 3TC358743XBG評估板參考手冊
  6. 1.36 MB  |  330次下載  |  免費
  7. 4DFM軟件使用教程
  8. 0.84 MB  |  295次下載  |  免費
  9. 5元宇宙深度解析—未來的未來-風口還是泡沫
  10. 6.40 MB  |  227次下載  |  免費
  11. 6迪文DGUS開發指南
  12. 31.67 MB  |  194次下載  |  免費
  13. 7元宇宙底層硬件系列報告
  14. 13.42 MB  |  182次下載  |  免費
  15. 8FP5207XR-G1中文應用手冊
  16. 1.09 MB  |  178次下載  |  免費

本月

  1. 1OrCAD10.5下載OrCAD10.5中文版軟件
  2. 0.00 MB  |  234315次下載  |  免費
  3. 2555集成電路應用800例(新編版)
  4. 0.00 MB  |  33566次下載  |  免費
  5. 3接口電路圖大全
  6. 未知  |  30323次下載  |  免費
  7. 4開關電源設計實例指南
  8. 未知  |  21549次下載  |  免費
  9. 5電氣工程師手冊免費下載(新編第二版pdf電子書)
  10. 0.00 MB  |  15349次下載  |  免費
  11. 6數字電路基礎pdf(下載)
  12. 未知  |  13750次下載  |  免費
  13. 7電子制作實例集錦 下載
  14. 未知  |  8113次下載  |  免費
  15. 8《LED驅動電路設計》 溫德爾著
  16. 0.00 MB  |  6656次下載  |  免費

總榜

  1. 1matlab軟件下載入口
  2. 未知  |  935054次下載  |  免費
  3. 2protel99se軟件下載(可英文版轉中文版)
  4. 78.1 MB  |  537798次下載  |  免費
  5. 3MATLAB 7.1 下載 (含軟件介紹)
  6. 未知  |  420027次下載  |  免費
  7. 4OrCAD10.5下載OrCAD10.5中文版軟件
  8. 0.00 MB  |  234315次下載  |  免費
  9. 5Altium DXP2002下載入口
  10. 未知  |  233046次下載  |  免費
  11. 6電路仿真軟件multisim 10.0免費下載
  12. 340992  |  191187次下載  |  免費
  13. 7十天學會AVR單片機與C語言視頻教程 下載
  14. 158M  |  183279次下載  |  免費
  15. 8proe5.0野火版下載(中文版免費下載)
  16. 未知  |  138040次下載  |  免費
亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>