การเขียน Multiplayer Text Adventure Engine ใน Node.js: การสร้าง Terminal Client (ตอนที่ 3)
เผยแพร่แล้ว: 2022-03-10ครั้งแรกที่ฉันแสดงให้คุณเห็นถึงวิธีการกำหนดโปรเจ็กต์เช่นนี้ และให้พื้นฐานของสถาปัตยกรรมรวมถึงกลไกที่อยู่เบื้องหลังเอ็นจิ้นเกมแก่คุณ จากนั้น ฉันแสดงให้คุณเห็นถึงการใช้งานพื้นฐานของเอ็นจิ้น — REST API พื้นฐานที่ให้คุณสำรวจโลกที่กำหนดโดย JSON
วันนี้ผมจะแสดงให้คุณเห็นถึงวิธีการสร้างไคลเอ็นต์ข้อความแบบเก่าสำหรับ API ของเราโดยใช้ Node.js
ส่วนอื่นๆ ของซีรีส์นี้
- ตอนที่ 1: บทนำ
- ส่วนที่ 2: การออกแบบเซิร์ฟเวอร์ Game Engine
- ตอนที่ 4: การเพิ่มแชทในเกมของเรา
ทบทวนการออกแบบดั้งเดิม
เมื่อฉันเสนอโครงร่างพื้นฐานสำหรับ UI ครั้งแรก ฉันได้เสนอสี่ส่วนบนหน้าจอ:
แม้ว่าในทางทฤษฎีจะดูถูกต้อง แต่ฉันก็พลาดความจริงที่ว่าการสลับไปมาระหว่างการส่งคำสั่งเกมและข้อความตัวอักษรจะเป็นเรื่องที่ยุ่งยาก ดังนั้นแทนที่จะให้ผู้เล่นเปลี่ยนโหมดด้วยตนเอง เราจะให้ตัวแยกวิเคราะห์คำสั่งทำให้แน่ใจว่าสามารถแยกแยะว่าเรา กำลังพยายามสื่อสารกับเกมหรือเพื่อนของเรา
ดังนั้น แทนที่จะมีสี่ส่วนในหน้าจอของเรา ตอนนี้เราจะมีสามส่วน:
นั่นคือภาพหน้าจอจริงของไคลเอนต์เกมสุดท้าย คุณจะเห็นหน้าจอเกมทางด้านซ้าย และแชททางด้านขวา โดยมีกล่องอินพุตทั่วไปเพียงกล่องเดียวที่ด้านล่าง โมดูลที่เราใช้ช่วยให้เราปรับแต่งสีและเอฟเฟกต์พื้นฐานบางอย่างได้ คุณจะสามารถโคลนโค้ดนี้จาก Github และทำสิ่งที่คุณต้องการด้วยรูปลักษณ์
ข้อแม้หนึ่งข้อแม้ว่า: แม้ว่าภาพหน้าจอด้านบนจะแสดงการแชทที่ทำงานเป็นส่วนหนึ่งของแอปพลิเคชัน แต่เราจะให้ความสำคัญกับบทความนี้โดยเน้นที่การตั้งค่าโครงการและกำหนดกรอบงานที่เราสามารถสร้างแอปพลิเคชันที่ใช้ข้อความ UI แบบไดนามิกได้ เราจะเน้นที่การเพิ่มการสนับสนุนทางแชทในบทต่อไปและตอนสุดท้ายของซีรีส์นี้
เครื่องมือที่เราต้องการ
แม้ว่าจะมีไลบรารี่มากมายที่ให้เราสร้างเครื่องมือ CLI ด้วย Node.js การเพิ่ม UI แบบข้อความเป็นสัตว์ร้ายที่แตกต่างไปจากเดิมอย่างสิ้นเชิง โดยเฉพาะอย่างยิ่ง ฉันสามารถหาห้องสมุดได้เพียงแห่งเดียว
ไลบรารีนี้มีประสิทธิภาพมาก และมีคุณลักษณะมากมายที่เราจะไม่ใช้สำหรับโปรเจ็กต์นี้ (เช่น การแสดงเงา การลากและวาง และอื่นๆ) โดยพื้นฐานแล้วจะปรับใช้ไลบรารี ncurses ทั้งหมดอีกครั้ง (ไลบรารี C ซึ่งอนุญาตให้นักพัฒนาสร้าง UI แบบข้อความ) ซึ่งไม่มีการโยง Node.js และทำใน JavaScript โดยตรง ดังนั้น หากจำเป็น เราสามารถตรวจสอบรหัสภายในของมันได้เป็นอย่างดี (ซึ่งผมไม่แนะนำเว้นแต่คุณจะต้องทำจริงๆ)
แม้ว่าเอกสารประกอบของ Blessed จะค่อนข้างกว้างขวาง แต่ส่วนใหญ่ประกอบด้วยรายละเอียดส่วนบุคคลเกี่ยวกับแต่ละวิธีที่จัดเตรียมไว้ (แทนที่จะมีแบบฝึกหัดอธิบายวิธีใช้วิธีการเหล่านั้นร่วมกันจริง ๆ ) และขาดตัวอย่างทุกที่จึงอาจเจาะลึกได้ยาก หากคุณต้องเข้าใจวิธีการทำงานเฉพาะ เมื่อคุณเข้าใจสิ่งนี้แล้ว ทุกอย่างทำงานในลักษณะเดียวกัน ซึ่งเป็นข้อดีอย่างมาก เนื่องจากไม่ใช่ทุกไลบรารีหรือแม้แต่ภาษา (ฉันกำลังดูคุณอยู่ PHP) มีไวยากรณ์ที่สอดคล้องกัน
แต่เอกสารกัน; ข้อดีที่สำคัญสำหรับไลบรารีนี้คือใช้งานได้ตามตัวเลือก JSON ตัวอย่างเช่น หากคุณต้องการวาดกล่องที่มุมบนขวาของหน้าจอ ให้ทำดังนี้:
var box = blessed.box({ top: '0', right: '0', width: '50%', height: '50%', content: 'Hello {bold}world{/bold}!', tags: true, border: { type: 'line' }, style: { fg: 'white', bg: 'magenta', border: { fg: '#f0f0f0' }, hover: { bg: 'green' } } });
อย่างที่คุณจินตนาการได้ แง่มุมอื่นๆ ของกล่องก็ถูกกำหนดไว้ด้วยเช่นกัน (เช่น ขนาดของกล่อง) ซึ่งสามารถปรับเปลี่ยนไดนามิกได้อย่างสมบูรณ์แบบตามขนาดของเทอร์มินัล ประเภทของเส้นขอบ และสี แม้กระทั่งสำหรับเหตุการณ์โฮเวอร์ หากคุณได้ทำการพัฒนา front-end มาก่อนแล้ว คุณจะพบว่าทั้งสองมีความเหลื่อมล้ำกันมาก
จุดที่ฉันพยายามทำที่นี่คือทุกอย่างที่เกี่ยวกับการแสดงกล่องได้รับการกำหนดค่าผ่านวัตถุ JSON ที่ส่งผ่านไปยังวิธีการของ box
สำหรับฉันแล้ว นั่นสมบูรณ์แบบเพราะฉันสามารถแยกเนื้อหานั้นลงในไฟล์การกำหนดค่า และสร้างตรรกะทางธุรกิจที่สามารถอ่านและตัดสินใจว่าจะวาดองค์ประกอบใดบนหน้าจอ ที่สำคัญที่สุด มันจะช่วยให้เราเห็นว่าพวกมันจะหน้าตาเป็นอย่างไรเมื่อวาดเสร็จแล้ว
นี่จะเป็นพื้นฐานสำหรับด้าน UI ทั้งหมดของโมดูลนี้ ( เพิ่มเติมในวินาทีนั้น! )
สถาปัตยกรรมของโมดูล
สถาปัตยกรรมหลักของโมดูลนี้อาศัยวิดเจ็ต UI ทั้งหมดที่เราจะแสดง กลุ่มของวิดเจ็ตเหล่านี้ถือเป็นหน้าจอ และหน้าจอทั้งหมดเหล่านี้ถูกกำหนดไว้ในไฟล์ JSON ไฟล์เดียว (ซึ่งคุณสามารถพบได้ในโฟลเดอร์ /config
)
ไฟล์นี้มีมากกว่า 250 บรรทัด การแสดงที่นี่ไม่สมเหตุสมผล คุณสามารถดูไฟล์แบบเต็มได้ทางออนไลน์ แต่ตัวอย่างข้อมูลขนาดเล็กจะมีลักษณะดังนี้:
"screens": { "main-options": { "file": "./main-options.js", "elements": { "username-request": { "type": "input-prompt", "params": { "position": { "top": "0%", "left": "0%", "width": "100%", "height": "25%" }, "content": "Input your username: ", "inputOnFocus": true, "border": { "type": "line" }, "style": { "fg": "white", "bg": "blue", "border": { "fg": "#f0f0f0" }, "hover": { "bg": "green" } } } }, "options": { "type": "window", "params": { "position": { "top": "25%", "left": "0%", "width": "100%", "height": "50%" }, "content": "Please select an option: \n1. Join an existing game.\n2. Create a new game", "border": { "type": "line" }, "style": { //... } } }, "input": { "type": "input", "handlerPath": "../lib/main-options-handler", //... } } }
องค์ประกอบ "หน้าจอ" จะมีรายการหน้าจอภายในแอปพลิเคชัน แต่ละหน้าจอมีรายการวิดเจ็ต (ซึ่งฉันจะอธิบายคร่าวๆ) และทุกวิดเจ็ตมีคำจำกัดความเฉพาะของพรและไฟล์ตัวจัดการที่เกี่ยวข้อง (ถ้ามี)
คุณสามารถดูได้ว่าองค์ประกอบ "params" ทุกรายการ (ภายในวิดเจ็ตเฉพาะ) แสดงถึงชุดพารามิเตอร์จริงที่คาดไว้โดยวิธีที่เราเห็นก่อนหน้านี้อย่างไร คีย์ที่เหลือกำหนดไว้ที่นั่นช่วยให้บริบทเกี่ยวกับประเภทของวิดเจ็ตที่จะแสดงผลและพฤติกรรมของวิดเจ็ต
จุดที่น่าสนใจบางประการ:
ตัวจัดการหน้าจอ
ทุกองค์ประกอบหน้าจอมีคุณสมบัติไฟล์ที่อ้างอิงรหัสที่เกี่ยวข้องกับหน้าจอนั้น รหัสนี้ไม่มีอะไรนอกจากวัตถุที่ต้องมีวิธีการ init
ต้น (ตรรกะการเริ่มต้นสำหรับหน้าจอนั้น ๆ จะเกิดขึ้นภายในนั้น) โดยเฉพาะเอ็นจิ้น UI หลักจะเรียกเมธอด init
ของทุกหน้าจอ ซึ่งในทางกลับกัน ควรรับผิดชอบในการเริ่มต้นตรรกะที่อาจต้องการ (เช่น การตั้งค่าเหตุการณ์ของกล่องอินพุต)
ต่อไปนี้เป็นรหัสสำหรับหน้าจอหลัก ซึ่งแอปพลิเคชันขอให้ผู้เล่นเลือกตัวเลือกในการเริ่มเกมใหม่หรือเข้าร่วมเกมที่มีอยู่:
const logger = require("../utils/logger") module.exports = { init: function(elements, UI) { this.elements = elements this.UI = UI this. this.setInput() }, moveToIDRequest: function(handler) { return this.UI.loadScreen('id-requests', (err, ) => { }) }, createNewGame: function(handler) { handler.createNewGame(this.UI.gamestate.APIKEY, (err, gameData) => { this.UI.gamestate.gameID = gameData._id handler.joinGame(this.UI.gamestate, (err) => { return this.UI.loadScreen('main-ui', { flashmessage: "You've joined game " + this.UI.gamestate.gameID + " successfully" }, (err, ) => { }) }) }) }, setInput: function() { let handler = require(this.elements["input"].meta.handlerPath) let input = this.elements["input"].obj let usernameRequest = this.elements['username-request'].obj let usernameRequestMeta = this.elements['username-request'].meta let question = usernameRequestMeta.params.content.trim() usernameRequest.setValue(question) this.UI.renderScreen() let validOptions = { 1: this.moveToIDRequest.bind(this), 2: this.createNewGame.bind(this) } usernameRequest.on('submit', (username) => { logger.info("Username:" +username) logger.info("Playername: " + username.replace(question, '')) this.UI.gamestate.playername = username.replace(question, '') input.focus() input.on('submit', (data) => { let command = input.getValue() if(!validOptions[+command]) { this.UI.setUpAlert("Invalid option: " + command) return this.UI.renderScreen() } return validOptions[+command](handler) }) }) return input } }
อย่างที่คุณเห็น วิธี init
เรียกเมธอด setupInput
ซึ่งโดยทั่วไปแล้วจะกำหนดค่าการเรียกกลับที่ถูกต้องเพื่อจัดการกับอินพุตของผู้ใช้ การโทรกลับนั้นถือตรรกะในการตัดสินใจว่าจะทำอย่างไรตามอินพุตของผู้ใช้ (ทั้ง 1 หรือ 2)
ตัวจัดการวิดเจ็ต
วิดเจ็ตบางตัว (โดยปกติคือวิดเจ็ตอินพุต) มีคุณสมบัติ handlerPath
ซึ่งอ้างอิงไฟล์ที่มีตรรกะเบื้องหลังคอมโพเนนต์นั้น ซึ่งไม่เหมือนกับตัวจัดการหน้าจอก่อนหน้า สิ่งเหล่านี้ไม่สนใจองค์ประกอบ UI มากนัก แต่จะจัดการกับตรรกะของกาวระหว่าง UI และไลบรารีใดก็ตามที่เราใช้เพื่อโต้ตอบกับบริการภายนอก (เช่น API ของเอ็นจิ้นเกม)
ประเภทวิดเจ็ต
เพิ่มเติมเล็กน้อยในคำจำกัดความ JSON ของวิดเจ็ตคือประเภท แทนที่จะใช้ชื่อที่ Blessed กำหนดไว้สำหรับพวกเขา ฉันกำลังสร้างชื่อใหม่เพื่อให้ฉันมีที่ว่างมากขึ้นสำหรับพฤติกรรมของพวกเขา ท้ายที่สุด วิดเจ็ตหน้าต่างอาจไม่ได้ “แสดงเพียงข้อมูล” เสมอไป หรือช่องป้อนข้อมูลอาจไม่ทำงานในลักษณะเดียวกันเสมอไป
ส่วนใหญ่เป็นการย้ายที่ยึดไว้เพียงเพื่อให้แน่ใจว่าฉันมีความสามารถนั้นหากฉันต้องการมันในอนาคต แต่อย่างที่คุณเห็น ฉันไม่ได้ใช้ส่วนประกอบประเภทต่างๆ มากมาย
หลายหน้าจอ
แม้ว่าหน้าจอหลักจะเป็นหน้าจอที่ฉันแสดงให้คุณเห็นในภาพหน้าจอด้านบน แต่เกมต้องการหน้าจออื่นๆ สองสามหน้าจอเพื่อขอสิ่งต่างๆ เช่น ชื่อผู้เล่นของคุณ หรือไม่ว่าคุณจะสร้างเซสชันเกมใหม่หรือเข้าร่วมในหน้าจอที่มีอยู่ วิธีที่ฉันจัดการนั่นคือ อีกครั้ง ผ่านคำจำกัดความของหน้าจอทั้งหมดเหล่านี้ในไฟล์ JSON เดียวกัน และเพื่อย้ายจากหน้าจอหนึ่งไปยังหน้าจอถัดไป เราใช้ตรรกะภายในไฟล์ตัวจัดการหน้าจอ
เราสามารถทำได้ง่ายๆ โดยใช้รหัสบรรทัดต่อไปนี้:
this.UI.loadScreen('main-ui', (err ) => { if(err) this.UI.setUpAlert(err) })
ฉันจะแสดงรายละเอียดเพิ่มเติมเกี่ยวกับคุณสมบัติ UI ในไม่กี่วินาที แต่ฉันแค่ใช้วิธี loadScreen
นั้นเพื่อแสดงผลหน้าจออีกครั้งและเลือกส่วนประกอบที่เหมาะสมจากไฟล์ JSON โดยใช้สตริงที่ส่งผ่านเป็นพารามิเตอร์ ตรงไปตรงมามาก
ตัวอย่างโค้ด
ตอนนี้ได้เวลาตรวจสอบเนื้อและมันฝรั่งของบทความนี้แล้ว: ตัวอย่างโค้ด ฉันแค่จะเน้นย้ำสิ่งที่ฉันคิดว่าเป็นอัญมณีเล็กๆ อยู่ข้างใน แต่คุณสามารถดูซอร์สโค้ดแบบเต็มได้โดยตรงในที่เก็บทุกเวลา
การใช้ไฟล์กำหนดค่าเพื่อสร้าง UI โดยอัตโนมัติ
ฉันได้กล่าวถึงบางส่วนแล้ว แต่ฉันคิดว่ามันคุ้มค่าที่จะสำรวจรายละเอียดเบื้องหลังตัวสร้างนี้ ส่วนสำคัญที่อยู่เบื้องหลังมัน (ไฟล์ index.js ภายในโฟลเดอร์ /ui
) คือมันเป็นตัวห่อหุ้มรอบวัตถุ Blessed และวิธีที่น่าสนใจที่สุดในนั้นก็คือวิธี loadScreen
วิธีนี้จะดึงการกำหนดค่า (ผ่านโมดูลการกำหนดค่า) สำหรับหน้าจอหนึ่งๆ และดำเนินการผ่านเนื้อหา โดยพยายามสร้างวิดเจ็ตที่เหมาะสมตามประเภทขององค์ประกอบแต่ละส่วน
loadScreen: function(sname, extras, done) { if(typeof extras == "function") { done = extras } let screen = config.get('screens.' + sname) let screenElems = {} if(this.screenElements.length > 0) { //remove previous screen this.screenElements.map( e => e.detach()) this.screen.realloc() } Object.keys(screen.elements).forEach( eName => { let elemObj = null let element = screen.elements[eName] if(element.type == 'window') { elemObj = this.setUpWindow(element) } if(element.type == 'input') { elemObj = this.setUpInputBox(element) } if(element.type == 'input-prompt') { elemObj = this.setUpInputBox(element) } screenElems[eName] = { meta: element, obj: elemObj } }) if(typeof extras === 'object' && extras.flashmessage) { this.setUpAlert(extras.flashmessage) } this.renderScreen() let logicPath = require(screen.file) logicPath.init(screenElems, this) done() },
อย่างที่คุณเห็น โค้ดค่อนข้างยาว แต่ตรรกะเบื้องหลังนั้นง่ายมาก:
- มันโหลดการกำหนดค่าสำหรับหน้าจอเฉพาะปัจจุบัน
- ล้างวิดเจ็ตที่มีอยู่ก่อนหน้านี้
- ผ่านทุกวิดเจ็ตและสร้างอินสแตนซ์
- หากมีการส่งการแจ้งเตือนพิเศษเป็นข้อความแฟลช (ซึ่งโดยพื้นฐานแล้วเป็นแนวคิดที่ฉันขโมยมาจาก Web Dev ซึ่งคุณตั้งค่าข้อความให้แสดงบนหน้าจอจนกว่าจะมีการรีเฟรชครั้งถัดไป)
- แสดงหน้าจอจริง
- และสุดท้าย ต้องใช้ตัวจัดการหน้าจอและดำเนินการตามวิธี "เริ่มต้น"
แค่นั้นแหละ! คุณสามารถตรวจสอบวิธีการที่เหลือได้ ซึ่งส่วนใหญ่เกี่ยวข้องกับวิดเจ็ตแต่ละรายการและวิธีแสดงผล
การสื่อสารระหว่าง UI และตรรกะทางธุรกิจ
แม้ว่าในระดับใหญ่ UI แบ็กเอนด์และเซิร์ฟเวอร์แชททั้งหมดมีการสื่อสารแบบแบ่งชั้นบ้าง ส่วนหน้านั้นต้องการสถาปัตยกรรมภายในอย่างน้อยสองชั้น ซึ่งองค์ประกอบ UI แท้จริงโต้ตอบกับชุดของฟังก์ชันที่แสดงถึงตรรกะหลักภายในโปรเจ็กต์นี้โดยเฉพาะ
ไดอะแกรมต่อไปนี้แสดงสถาปัตยกรรมภายในสำหรับไคลเอ็นต์ข้อความที่เรากำลังสร้าง:
ให้ฉันอธิบายเพิ่มเติมเล็กน้อย ดังที่ได้กล่าวไว้ข้างต้น loadScreenMethod
จะสร้างการนำเสนอ UI ของวิดเจ็ต (สิ่งเหล่านี้เป็นอ็อบเจกต์ที่ได้รับพร) แต่สิ่งเหล่านี้ถูกรวมไว้เป็นส่วนหนึ่งของวัตถุตรรกะหน้าจอ ซึ่งเป็นที่ที่เราตั้งค่าเหตุการณ์พื้นฐาน (เช่น onSubmit
สำหรับกล่องอินพุต)
ให้ฉันยกตัวอย่างที่เป็นประโยชน์กับคุณ นี่คือหน้าจอแรกที่คุณเห็นเมื่อเริ่มต้นไคลเอ็นต์ UI:
มีสามส่วนบนหน้าจอนี้:
- คำขอชื่อผู้ใช้
- ตัวเลือกเมนู / ข้อมูล
- หน้าจออินพุตสำหรับตัวเลือกเมนู
โดยพื้นฐานแล้ว สิ่งที่เราต้องทำคือขอชื่อผู้ใช้แล้วขอให้พวกเขาเลือกหนึ่งในสองตัวเลือก (ไม่ว่าจะเริ่มเกมใหม่หรือเข้าร่วมเกมที่มีอยู่)
รหัสที่ดูแลมีดังต่อไปนี้:
module.exports = { init: function(elements, UI) { this.elements = elements this.UI = UI this. this.setInput() }, moveToIDRequest: function(handler) { return this.UI.loadScreen('id-requests', (err, ) => { }) }, createNewGame: function(handler) { handler.createNewGame(this.UI.gamestate.APIKEY, (err, gameData) => { this.UI.gamestate.gameID = gameData._id handler.joinGame(this.UI.gamestate, (err) => { return this.UI.loadScreen('main-ui', { flashmessage: "You've joined game " + this.UI.gamestate.gameID + " successfully" }, (err, ) => { }) }) }) }, setInput: function() { let handler = require(this.elements["input"].meta.handlerPath) let input = this.elements["input"].obj let usernameRequest = this.elements['username-request'].obj let usernameRequestMeta = this.elements['username-request'].meta let question = usernameRequestMeta.params.content.trim() usernameRequest.setValue(question) this.UI.renderScreen() let validOptions = { 1: this.moveToIDRequest.bind(this), 2: this.createNewGame.bind(this) } usernameRequest.on('submit', (username) => { logger.info("Username:" +username) logger.info("Playername: " + username.replace(question, '')) this.UI.gamestate.playername = username.replace(question, '') input.focus() input.on('submit', (data) => { let command = input.getValue() if(!validOptions[+command]) { this.UI.setUpAlert("Invalid option: " + command) return this.UI.renderScreen() } return validOptions[+command](handler) }) }) return input } }
ฉันรู้ว่าโค้ดเยอะ แต่เน้นที่วิธีการ init
สิ่งสุดท้ายที่ทำคือการเรียกเมธอด setInput
ซึ่งดูแลการเพิ่มเหตุการณ์ที่ถูกต้องลงในกล่องอินพุตที่ถูกต้อง
ดังนั้น ด้วยบรรทัดเหล่านี้:
let handler = require(this.elements["input"].meta.handlerPath) let input = this.elements["input"].obj let usernameRequest = this.elements['username-request'].obj let usernameRequestMeta = this.elements['username-request'].meta let question = usernameRequestMeta.params.content.trim()
เรากำลังเข้าถึงวัตถุที่ได้รับพรและได้รับการอ้างอิง เพื่อให้เราสามารถตั้งค่าเหตุการณ์การ submit
ได้ในภายหลัง ดังนั้น หลังจากที่เราส่งชื่อผู้ใช้ เราจะเปลี่ยนโฟกัสไปที่ช่องป้อนข้อมูลที่สอง (ตามตัวอักษรด้วย input.focus()
)
ขึ้นอยู่กับตัวเลือกที่เราเลือกจากเมนู เรากำลังเรียกใช้วิธีใดวิธีหนึ่งดังต่อไปนี้:
-
createNewGame
: สร้างเกมใหม่โดยโต้ตอบกับตัวจัดการที่เกี่ยวข้อง -
moveToIDRequest
: แสดงหน้าจอถัดไปเพื่อขอ ID เกมเพื่อเข้าร่วม
การสื่อสารกับ Game Engine
สุดท้ายแต่ไม่ท้ายสุด (และทำตามตัวอย่างด้านบน) หากคุณกด 2 คุณจะสังเกตเห็นว่าเมธอด createNewGame
ใช้เมธอดของตัวจัดการ createNewGame
แล้ว joinGame
เข้าร่วมเกม (เข้าร่วมเกมทันทีหลังจากสร้างเกม)
ทั้งสองวิธีนี้มีขึ้นเพื่อทำให้การโต้ตอบกับ API ของ Game Engine ง่ายขึ้น นี่คือรหัสสำหรับตัวจัดการของหน้าจอนี้:
const request = require("request"), config = require("config"), apiClient = require("./apiClient") let API = config.get("api") module.exports = { joinGame: function(apikey, gameId, cb) { apiClient.joinGame(apikey, gameId, cb) }, createNewGame: function(apikey, cb) { request.post(API.url + API.endpoints.games + "?apikey=" + apikey, { //creating game body: { cartridgeid: config.get("app.game.cartdrigename") }, json: true }, (err, resp, body) => { cb(null, body) }) } }
คุณจะเห็นสองวิธีในการจัดการกับพฤติกรรมนี้ วิธีแรกใช้คลาส apiClient
ซึ่งรวมการโต้ตอบกับ GameEngine ไว้ในอีกชั้นหนึ่งของการเป็นนามธรรม
วิธีที่สองดำเนินการโดยตรงโดยส่งคำขอ POST ไปยัง URL ที่ถูกต้องพร้อมเพย์โหลดที่ถูกต้อง ไม่มีอะไรแฟนซีทำหลังจากนั้น เราแค่ส่งเนื้อความของการตอบกลับกลับไปที่ตรรกะ UI
หมายเหตุ : หากคุณสนใจในเวอร์ชันเต็มของซอร์สโค้ดสำหรับลูกค้ารายนี้ คุณสามารถตรวจสอบได้ที่นี่
คำพูดสุดท้าย
นี่เป็นโปรแกรมสำหรับไคลเอ็นต์แบบข้อความสำหรับการผจญภัยแบบข้อความของเรา ฉันครอบคลุม:
- วิธีจัดโครงสร้างแอปพลิเคชันไคลเอนต์
- ฉันใช้ Blessed เป็นเทคโนโลยีหลักในการสร้างเลเยอร์การนำเสนอได้อย่างไร
- วิธีจัดโครงสร้างการโต้ตอบกับบริการแบ็คเอนด์จากไคลเอนต์ที่ซับซ้อน
- และหวังว่าจะมีที่เก็บเต็มรูปแบบ
และในขณะที่ UI อาจดูไม่เหมือนเวอร์ชันดั้งเดิมทุกประการ แต่ก็บรรลุวัตถุประสงค์ หวังว่าบทความนี้จะทำให้คุณมีแนวคิดเกี่ยวกับการออกแบบความพยายามดังกล่าว และคุณมีแนวโน้มที่จะลองทำด้วยตัวเองในอนาคต Blessed เป็นเครื่องมือที่ทรงพลังอย่างแน่นอน แต่คุณจะต้องอดทนกับมันในขณะที่เรียนรู้วิธีใช้งานและวิธีนำทางผ่านเอกสารของพวกเขา
ในตอนต่อไปและตอนสุดท้าย ฉันจะพูดถึงวิธีที่ฉันเพิ่มเซิร์ฟเวอร์การแชททั้งในส่วนแบ็คเอนด์และสำหรับไคลเอ็นต์ข้อความนี้
เจอกันใหม่ตอนหน้าค่ะ!
ส่วนอื่นๆ ของซีรีส์นี้
- ตอนที่ 1: บทนำ
- ส่วนที่ 2: การออกแบบเซิร์ฟเวอร์ Game Engine
- ตอนที่ 4: การเพิ่มแชทในเกมของเรา